﻿#region Copyright (c) 10/02/2012, Olaf Kober <amarok.projects@gmail.com>
//================================================================================
//	Permission is hereby granted, free of charge, to any person obtaining a copy
//	of this software and associated documentation files (the "Software"), to deal
//	in the Software without restriction, including without limitation the rights
//	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//	copies of the Software, and to permit persons to whom the Software is
//	furnished to do so, subject to the following conditions:
//
//	The above copyright notice and this permission notice shall be included in
//	all copies or substantial portions of the Software.
//
//	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//	THE SOFTWARE.
//================================================================================
#endregion

using System;
using System.ComponentModel;
using System.Globalization;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interop;
using Amarok.Agents;


namespace Amarok.Presentation
{
	/// <summary>
	/// This type represents a window that realizes the IView contract.
	/// </summary>
	public class ViewWindow : Window,
		IView
	{
		// data
		private readonly ViewDispatcher mDispatcher;


		#region ++ Dependency Property: WindowCloseCommand ++

		/// <summary>
		/// </summary>
		public static readonly DependencyProperty WindowCloseCommandProperty = DependencyProperty.Register(
			"WindowCloseCommand",
			typeof(ICommand),
			typeof(ViewWindow),
			new UIPropertyMetadata(
				null
			));


		/// <summary>
		/// </summary>
		[
			Bindable(true),
			CustomPropertiesCategory
		]
		public ICommand WindowCloseCommand
		{
			// IMPORTANT: To maintain parity between setting a property in XAML and procedural code, 
			//			  do not touch the getter and setter inside this dependency property!
			get
			{
				return (ICommand)GetValue(WindowCloseCommandProperty);
			}
			set
			{
				SetValue(WindowCloseCommandProperty, value);
			}
		}

		#endregion

		#region ++ Dependency Property: WindowCloseCommandParameter ++

		/// <summary>
		/// </summary>
		public static readonly DependencyProperty WindowCloseCommandParameterProperty = DependencyProperty.Register(
			"WindowCloseCommandParameter",
			typeof(Object),
			typeof(ViewWindow),
			new UIPropertyMetadata(
				null
			));


		/// <summary>
		/// </summary>
		[
			Bindable(true),
			CustomPropertiesCategory
		]
		public Object WindowCloseCommandParameter
		{
			// IMPORTANT: To maintain parity between setting a property in XAML and procedural code, 
			//			  do not touch the getter and setter inside this dependency property!
			get
			{
				return (Object)GetValue(WindowCloseCommandParameterProperty);
			}
			set
			{
				SetValue(WindowCloseCommandParameterProperty, value);
			}
		}

		#endregion

		#region ++ IView Interface ++

		/// <summary>
		/// Gets or sets the view model associated with the view.
		/// </summary>
		/// 
		/// <exception cref="InvalidOperationException">
		/// The calling thread does not have access to this System.Windows.Threading.DispatcherObject.</exception>
		public Object ViewModel
		{
			get
			{
				base.VerifyAccess();
				return this.DataContext;
			}
			set
			{
				base.VerifyAccess();
				this.DataContext = value;
			}
		}

		/// <summary>
		/// Gets a reference to the control that implements the view.
		/// </summary>
		/// 
		/// <exception cref="InvalidOperationException">
		/// The calling thread does not have access to this System.Windows.Threading.DispatcherObject.</exception>
		public FrameworkElement Control
		{
			get
			{
				base.VerifyAccess();
				return this;
			}
		}


		/// <summary>
		/// Executes the specified action synchronously on the thread that the view was created on.
		/// </summary>
		/// 
		/// <param name="action">
		/// The action to execute, which takes no arguments and does not return a value.</param>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public void Invoke(Action action)
		{
			ViewHelper.Invoke(this, action);
		}

		/// <summary>
		/// Executes the specified action asynchronously on the thread that the view was created on.
		/// </summary>
		/// 
		/// <param name="action">
		/// The action to execute, which takes no arguments and does not return a value.</param>
		/// 
		/// <returns>
		/// A task that represents the asynchronously running action. This task can be used to wait for completion, 
		/// to register a continuation and to access the state of the action.</returns>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public Task InvokeAsync(Action action)
		{
			return ViewHelper.InvokeAsync(this, action);
		}


		/// <summary>
		/// Sends the specified view action synchronously to the thread that the view was created on, and 
		/// raises the view action attached event on the view.
		/// </summary>
		/// 
		/// <param name="viewAction">
		/// The view action to send to the view.</param>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public void Send(ViewAction viewAction)
		{
			ViewHelper.Send(this, viewAction);
		}

		/// <summary>
		/// Sends the specified view action asynchronously to the thread that the view was created on, and 
		/// raises the view action attached event on the view.
		/// </summary>
		/// 
		/// <param name="viewAction">
		/// The view action to send to the view.</param>
		/// 
		/// <returns>
		/// A task that represents the asynchronously running view action. This task can be used to wait for 
		/// completion, to register a continuation and to access the state of the view action execution.</returns>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public Task SendAsync(ViewAction viewAction)
		{
			return ViewHelper.SendAsync(this, viewAction);
		}

		#endregion

		#region ++ Public Interface ++

		/// <summary>
		/// Initializes a new instance.
		/// </summary>
		public ViewWindow()
		{
			if (Designer.IsRuntimeMode)
			{
				mDispatcher = ViewHelper.CreateDispatcher(this);
				ViewHelper.SetupViewActionEventHandler(this, mDispatcher);
				ViewHelper.SetupLoadedEventHandler(this, OnLocalize, OnLoaded);
			}
		}

		#endregion

		#region ++ Public Interface (ShowWindow) ++

		/// <summary>
		/// Shows a non-modal window specified by the supplied window description.
		/// </summary>
		/// 
		/// <param name="windowDescription">
		/// The window description that specifies which window should be shown.</param>
		/// <param name="environment">
		/// The agent environment that should be forwarded to the window's controller.</param>
		/// 
		/// <exception cref="InvalidOperationException">
		/// The calling thread does not have access to this System.Windows.Threading.DispatcherObject.</exception>
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public virtual void ShowWindow(WindowDescription windowDescription, AgentEnvironment environment)
		{
			this.ShowWindow(
				windowDescription,
				environment,
				AgentOptions.Empty);
		}

		/// <summary>
		/// Shows a non-modal window specified by the supplied window description.
		/// </summary>
		/// 
		/// <param name="windowDescription">
		/// The window description that specifies which window should be shown.</param>
		/// <param name="environment">
		/// The agent environment that should be forwarded to the window's controller.</param>
		/// <param name="options">
		/// The agent options that should be forwarded to the window's controller.</param>
		/// 
		/// <exception cref="InvalidOperationException">
		/// The calling thread does not have access to this System.Windows.Threading.DispatcherObject.</exception>
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public virtual void ShowWindow(WindowDescription windowDescription, AgentEnvironment environment,
			AgentOptions options)
		{
			base.VerifyAccess();

			Verify.NotNull(windowDescription, "windowDescription");
			Verify.NotNull(environment, "environment");
			Verify.NotNull(options, "options");

			windowDescription.Create(environment, options);
			windowDescription.Window.Show();
		}

		#endregion

		#region ++ Public Interface (ShowDialog) ++

		/// <summary>
		/// Shows a modal dialog window specified by the supplied window description. The shown dialog window 
		/// can return a dialog result, which is returned by this method.
		/// </summary>
		/// 
		/// <param name="windowDescription">
		/// The window description that specifies which dialog window should be shown.</param>
		/// <param name="environment">
		/// The agent environment that should be forwarded to the window's controller.</param>
		/// 
		/// <returns>
		/// The dialog result of the shown dialog window.</returns>
		/// 
		/// <exception cref="InvalidOperationException">
		/// The calling thread does not have access to this System.Windows.Threading.DispatcherObject.</exception>
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public virtual Nullable<Boolean> ShowDialog(WindowDescription windowDescription, AgentEnvironment environment)
		{
			return this.ShowDialog(
				windowDescription,
				environment,
				AgentOptions.Empty);
		}

		/// <summary>
		/// Shows a modal dialog window specified by the supplied window description. The shown dialog window 
		/// can return a dialog result, which is returned by this method.
		/// </summary>
		/// 
		/// <param name="windowDescription">
		/// The window description that specifies which dialog window should be shown.</param>
		/// <param name="environment">
		/// The agent environment that should be forwarded to the window's controller.</param>
		/// <param name="options">
		/// The agent options that should be forwarded to the window's controller.</param>
		/// 
		/// <returns>
		/// The dialog result of the shown dialog window.</returns>
		/// 
		/// <exception cref="InvalidOperationException">
		/// The calling thread does not have access to this System.Windows.Threading.DispatcherObject.</exception>
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public virtual Nullable<Boolean> ShowDialog(WindowDescription windowDescription, AgentEnvironment environment,
			AgentOptions options)
		{
			base.VerifyAccess();

			Verify.NotNull(windowDescription, "windowDescription");
			Verify.NotNull(environment, "environment");
			Verify.NotNull(options, "options");

			windowDescription.Create(environment, options);
			windowDescription.Window.Owner = this;
			return windowDescription.Window.ShowDialog();
		}

		#endregion

		#region ++ Public Interface (Show Message Boxes) ++

		/// <summary>
		/// Displays an information message box in front of this window.
		/// </summary>
		/// 
		/// <param name="message">
		/// A string that specifies the text to display.</param>
		/// <param name="caption">
		/// A string that specifies the title bar caption to display.</param>
		/// <param name="arguments">
		/// An optional list of arguments formatted into the message and caption.</param>
		/// 
		/// <exception cref="InvalidOperationException">
		/// The calling thread does not have access to this System.Windows.Threading.DispatcherObject.</exception>
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		/// <exception cref="ArgumentException">
		/// An empty string was passed to a method that did not accept it as a valid argument.</exception>
		public virtual void ShowInformationMessageBox(String message, String caption, params Object[] arguments)
		{
			this.ShowMessageBox(
				message,
				caption,
				MessageBoxButton.OK,
				MessageBoxImage.Information,
				MessageBoxResult.OK,
				arguments);
		}

		/// <summary>
		/// Displays a warning message box in front of this window.
		/// </summary>
		/// 
		/// <param name="message">
		/// A string that specifies the text to display.</param>
		/// <param name="caption">
		/// A string that specifies the title bar caption to display.</param>
		/// <param name="arguments">
		/// An optional list of arguments formatted into the message and caption.</param>
		/// 
		/// <exception cref="InvalidOperationException">
		/// The calling thread does not have access to this System.Windows.Threading.DispatcherObject.</exception>
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		/// <exception cref="ArgumentException">
		/// An empty string was passed to a method that did not accept it as a valid argument.</exception>
		public virtual void ShowWarningMessageBox(String message, String caption, params Object[] arguments)
		{
			this.ShowMessageBox(
				message,
				caption,
				MessageBoxButton.OK,
				MessageBoxImage.Warning,
				MessageBoxResult.OK,
				arguments);
		}

		/// <summary>
		/// Displays an error message box in front of this window.
		/// </summary>
		/// 
		/// <param name="message">
		/// A string that specifies the text to display.</param>
		/// <param name="caption">
		/// A string that specifies the title bar caption to display.</param>
		/// <param name="arguments">
		/// An optional list of arguments formatted into the message and caption.</param>
		/// 
		/// <exception cref="InvalidOperationException">
		/// The calling thread does not have access to this System.Windows.Threading.DispatcherObject.</exception>
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		/// <exception cref="ArgumentException">
		/// An empty string was passed to a method that did not accept it as a valid argument.</exception>
		public virtual void ShowErrorMessageBox(String message, String caption, params Object[] arguments)
		{
			this.ShowMessageBox(
				message,
				caption,
				MessageBoxButton.OK,
				MessageBoxImage.Error,
				MessageBoxResult.OK,
				arguments);
		}

		/// <summary>
		/// Displays a confirmation (yes, no) message box in front of this window.
		/// </summary>
		/// 
		/// <param name="message">
		/// A string that specifies the text to display.</param>
		/// <param name="caption">
		/// A string that specifies the title bar caption to display.</param>
		/// <param name="arguments">
		/// An optional list of arguments formatted into the message and caption.</param>
		/// 
		/// <exception cref="InvalidOperationException">
		/// The calling thread does not have access to this System.Windows.Threading.DispatcherObject.</exception>
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		/// <exception cref="ArgumentException">
		/// An empty string was passed to a method that did not accept it as a valid argument.</exception>
		public virtual Boolean ShowConfirmationMessageBox(String message, String caption, params Object[] arguments)
		{
			var result = this.ShowMessageBox(
				message,
				caption,
				MessageBoxButton.YesNo,
				MessageBoxImage.Question,
				MessageBoxResult.No,
				arguments);

			return result == MessageBoxResult.Yes;
		}

		/// <summary>
		/// Displays a message box in front of this window.
		/// </summary>
		/// 
		/// <param name="message">
		/// A string that specifies the text to display.</param>
		/// <param name="caption">
		/// A string that specifies the title bar caption to display.</param>
		/// <param name="button">
		/// A value that specifies which button or buttons to display.</param>
		/// <param name="icon">
		/// A value that specifies the icon to display.</param>
		/// <param name="defaultResult">
		/// A value that specifies the default result of the message box.</param>
		/// <param name="arguments">
		/// An optional list of arguments formatted into the message and caption.</param>
		/// 
		/// <returns>
		/// A value that specifies which message box button is clicked by the user.</returns>
		/// 
		/// <exception cref="InvalidOperationException">
		/// The calling thread does not have access to this System.Windows.Threading.DispatcherObject.</exception>
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		/// <exception cref="ArgumentException">
		/// An empty string was passed to a method that did not accept it as a valid argument.</exception>
		public virtual MessageBoxResult ShowMessageBox(String message, String caption, MessageBoxButton button,
			MessageBoxImage icon, MessageBoxResult defaultResult, params Object[] arguments)
		{
			base.VerifyAccess();

			Verify.NotEmpty(message, "message");
			Verify.NotEmpty(caption, "caption");

			var formattedMessage = String.Format(CultureInfo.CurrentCulture,
				message, arguments);
			var formattedCaption = String.Format(CultureInfo.CurrentCulture,
				caption, arguments);

			return MessageBox.Show(this,
				formattedMessage,
				formattedCaption,
				button,
				icon,
				defaultResult,
				MessageBoxOptions.None);
		}

		#endregion

		#region ## Overridable Methods ##

		/// <summary>
		/// This overridable method is called once after the view has been loaded. This method is called directly
		/// before <see cref="OnLoaded"/>. Override this method to localize static text resources.
		/// </summary>
		protected virtual void OnLocalize()
		{
			// default implementation
		}

		/// <summary>
		/// This overridable method is called once after the view has been loaded. This method is called directly
		/// after <see cref="OnLocalize"/>. Override this method to implement specific logic requiring the view to 
		/// be fully initialized and loaded, e.g. the entire tree of UI elements is loaded and the view model is 
		/// assigned to the view at this moment.
		/// </summary>
		protected virtual void OnLoaded()
		{
			// default implementation
		}

		#endregion

		#region Implementation (Windows, Dialogs)

		[ViewActionHandler]
		private Boolean _HandleShowWindowAction(ShowWindowAction action)
		{
			this.ShowWindow(
				action.WindowDescription,
				action.Environment,
				action.Options);

			return true;
		}

		[ViewActionHandler]
		private Boolean _HandleShowDialogAction(ShowDialogAction action)
		{
			action.DialogResult = this.ShowDialog(
				action.WindowDescription,
				action.Environment,
				action.Options);

			return true;
		}

		#endregion

		#region Implementation (Message Boxes)

		[ViewActionHandler]
		private Boolean _HandleShowInformationMessageBoxAction(ShowInformationMessageBoxAction action)
		{
			this.ShowInformationMessageBox(
				action.Message,
				action.Caption,
				action.Arguments);

			return true;
		}

		[ViewActionHandler]
		private Boolean _HandleShowWarningMessageBoxAction(ShowWarningMessageBoxAction action)
		{
			this.ShowWarningMessageBox(
				action.Message,
				action.Caption,
				action.Arguments);

			return true;
		}

		[ViewActionHandler]
		private Boolean _HandleShowErrorMessageBoxAction(ShowErrorMessageBoxAction action)
		{
			this.ShowErrorMessageBox(
				action.Message,
				action.Caption,
				action.Arguments);

			return true;
		}

		[ViewActionHandler]
		private Boolean _HandleShowConfirmationMessageBoxAction(ShowConfirmationMessageBoxAction action)
		{
			action.Confirmed = this.ShowConfirmationMessageBox(
				action.Message,
				action.Caption,
				action.Arguments);

			return true;
		}

		#endregion

		#region Implementation (Window Close)

		// state
		private Boolean mWindowCloseRequested;
		private Boolean mWindowIsClosing;
		private Boolean mWindowHasClosed;


		[ViewActionHandler]
		private Boolean _HandleCloseWindowAction(CloseWindowAction action)
		{
			mWindowCloseRequested = true;

			if (ComponentDispatcher.IsThreadModal)
			{
				if (action.DialogResult.HasValue)
				{
					base.DialogResult = action.DialogResult;
				}
				else
				{
					base.DialogResult = true;
				}
			}
			else
			{
				if (mWindowIsClosing == false)
					base.Close();
			}

			return true;
		}

		/// <summary>
		/// </summary>
		protected override void OnClosing(CancelEventArgs e)
		{
			mWindowIsClosing = true;

			if (mWindowCloseRequested == false)
			{
				var command = this.WindowCloseCommand;
				var parameter = this.WindowCloseCommandParameter;

				if (command != null)
				{
					if (command.CanExecute(parameter))
						command.Execute(parameter);

					if (mWindowCloseRequested == false)
						e.Cancel = true;
					else
						e.Cancel = false;
				}
			}

			if (e.Cancel == true)
				mWindowIsClosing = false;

			base.OnClosing(e);
		}

		/// <summary>
		/// </summary>
		protected override void OnClosed(EventArgs e)
		{
			if (mWindowHasClosed)
				return;

			mWindowHasClosed = true;

			base.OnClosed(e);
		}

		#endregion

	}
}
